{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook shows how to replace the `VolatileMemoryStore` memory storage used in a [previous notebook](./06-memory-and-embeddings.ipynb) with a `WeaviateMemoryStore`.\n",
    "\n",
    "`WeaviateMemoryStore` is an example of a persistent (i.e. long-term) memory store backed by the Weaviate vector database.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# About Weaviate\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Weaviate](https://weaviate.io/) is an open-source vector database designed to scale seamlessly into billions of data objects. This implementation supports hybrid search out-of-the-box (meaning it will perform better for keyword searches).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can run Weaviate in 5 ways:\n",
    "\n",
    "- **SaaS** – with [Weaviate Cloud Services (WCS)](https://weaviate.io/pricing).\n",
    "\n",
    "  WCS is a fully managed service that takes care of hosting, scaling, and updating your Weaviate instance. You can try it out for free with a sandbox that lasts for 14 days.\n",
    "\n",
    "  To set up a SaaS Weaviate instance with WCS:\n",
    "\n",
    "  1.  Navigate to [Weaviate Cloud Console](https://console.weaviate.cloud/).\n",
    "  2.  Register or sign in to your WCS account.\n",
    "  3.  Create a new cluster with the following settings:\n",
    "      - `Subscription Tier` – Free sandbox for a free trial, or contact [hello@weaviate.io](mailto:hello@weaviate.io) for other options.\n",
    "      - `Cluster name` – a unique name for your cluster. The name will become part of the URL used to access this instance.\n",
    "      - `Enable Authentication?` – Enabled by default. This will generate a static API key that you can use to authenticate.\n",
    "  4.  Wait for a few minutes until your cluster is ready. You will see a green tick ✔️ when it's done. Copy your cluster URL.\n",
    "\n",
    "- **Hybrid SaaS**\n",
    "\n",
    "  > If you need to keep your data on-premise for security or compliance reasons, Weaviate also offers a Hybrid SaaS option: Weaviate runs within your cloud instances, but the cluster is managed remotely by Weaviate. This gives you the benefits of a managed service without sending data to an external party.\n",
    "\n",
    "  The Weaviate Hybrid SaaS is a custom solution. If you are interested in this option, please reach out to [hello@weaviate.io](mailto:hello@weaviate.io).\n",
    "\n",
    "- **Self-hosted** – with a Docker container\n",
    "\n",
    "  To set up a Weaviate instance with Docker:\n",
    "\n",
    "  1. [Install Docker](https://docs.docker.com/engine/install/) on your local machine if it is not already installed.\n",
    "  2. [Install the Docker Compose Plugin](https://docs.docker.com/compose/install/)\n",
    "  3. Download a `docker-compose.yml` file with this `curl` command:\n",
    "\n",
    "     ```\n",
    "     curl -o docker-compose.yml \"https://configuration.weaviate.io/v2/docker-compose/docker-compose.yml?modules=standalone&runtime=docker-compose&weaviate_version=v1.19.6\"\n",
    "     ```\n",
    "\n",
    "     Alternatively, you can use Weaviate's docker compose [configuration tool](https://weaviate.io/developers/weaviate/installation/docker-compose) to generate your own `docker-compose.yml` file.\n",
    "\n",
    "  4. Run `docker compose up -d` to spin up a Weaviate instance.\n",
    "\n",
    "     > To shut it down, run `docker compose down`.\n",
    "\n",
    "- **Self-hosted** – with a Kubernetes cluster\n",
    "\n",
    "  To configure a self-hosted instance with Kubernetes, follow Weaviate's [documentation](https://weaviate.io/developers/weaviate/installation/kubernetes).|\n",
    "\n",
    "- **Embedded** - start a weaviate instance right from your application code using the client library\n",
    "\n",
    "  This code snippet shows how to instantiate an embedded weaviate instance and upload a document:\n",
    "\n",
    "  ```python\n",
    "  import weaviate\n",
    "  from weaviate.embedded import EmbeddedOptions\n",
    "\n",
    "  client = weaviate.Client(\n",
    "    embedded_options=EmbeddedOptions()\n",
    "  )\n",
    "\n",
    "  data_obj = {\n",
    "    \"name\": \"Chardonnay\",\n",
    "    \"description\": \"Goes with fish\"\n",
    "  }\n",
    "\n",
    "  client.data_object.create(data_obj, \"Wine\")\n",
    "  ```\n",
    "\n",
    "  Refer to the [documentation](https://weaviate.io/developers/weaviate/installation/embedded) for more details about this deployment method.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Setup\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install semantic-kernel==1.0.1\n",
    "!pip install weaviate-client\n",
    "!pip install python-dotenv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## OS-specific notes:\n",
    "\n",
    "- if you run into SSL errors when connecting to OpenAI on macOS, see this issue for a [potential solution](https://github.com/microsoft/semantic-kernel/issues/627#issuecomment-1580912248)\n",
    "- on Windows, you may need to run Docker Desktop as administrator\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, we instantiate the Weaviate memory store. Uncomment ONE of the options below, depending on how you want to use Weaviate:\n",
    "\n",
    "- from a Docker instance\n",
    "- from WCS\n",
    "- directly from the client (embedded Weaviate), which works on Linux only at the moment\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.connectors.memory.weaviate import weaviate_memory_store\n",
    "\n",
    "# Note the Weaviate Config values need to be either configured as environment variables\n",
    "# or in the .env file, as a back up. When creating the instance of the `weaviate_memory_store`\n",
    "# pass in `env_file_path=<path_to_file>` to read the config values from the `.env` file, otherwise\n",
    "# the values will be read from environment variables.\n",
    "# Env variables or .env file config should look like:\n",
    "# WEAVIATE_URL=\"http://localhost:8080\"\n",
    "# WEAVIATE_API_KEY=\"\"\n",
    "# WEAVIATE_USE_EMBED=True|False\n",
    "\n",
    "store = weaviate_memory_store.WeaviateMemoryStore()\n",
    "store.client.schema.delete_all()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, we register the memory store to the kernel:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from services import Service\n",
    "\n",
    "# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
    "selectedService = Service.OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding\n",
    "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n",
    "from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore\n",
    "from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin\n",
    "\n",
    "kernel = Kernel()\n",
    "\n",
    "chat_service_id = \"chat\"\n",
    "if selectedService == Service.OpenAI:\n",
    "    oai_chat_service = OpenAIChatCompletion(service_id=chat_service_id, ai_model_id=\"gpt-3.5-turbo\")\n",
    "    embedding_gen = OpenAITextEmbedding(ai_model_id=\"text-embedding-ada-002\")\n",
    "    kernel.add_service(oai_chat_service)\n",
    "    kernel.add_service(embedding_gen)\n",
    "\n",
    "memory = SemanticTextMemory(storage=VolatileMemoryStore(), embeddings_generator=embedding_gen)\n",
    "kernel.add_plugin(TextMemoryPlugin(memory), \"TextMemoryPlugin\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Manually adding memories\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's create some initial memories \"About Me\". We can add memories to our weaviate memory store by using `save_information`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "collection_id = \"generic\"\n",
    "\n",
    "\n",
    "async def populate_memory(memory: SemanticTextMemory) -> None:\n",
    "    # Add some documents to the semantic memory\n",
    "    await memory.save_information(collection=collection_id, id=\"info1\", text=\"Your budget for 2024 is $100,000\")\n",
    "    await memory.save_information(collection=collection_id, id=\"info2\", text=\"Your savings from 2023 are $50,000\")\n",
    "    await memory.save_information(collection=collection_id, id=\"info3\", text=\"Your investments are $80,000\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "await populate_memory(memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Searching is done through `search`:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "async def search_memory_examples(memory: SemanticTextMemory) -> None:\n",
    "    questions = [\"What is my budget for 2024?\", \"What are my savings from 2023?\", \"What are my investments?\"]\n",
    "\n",
    "    for question in questions:\n",
    "        print(f\"Question: {question}\")\n",
    "        result = await memory.search(collection_id, question)\n",
    "        print(f\"Answer: {result[0].text}\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "await search_memory_examples(memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's how to use the weaviate memory store in a chat application:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.functions.kernel_function import KernelFunction\n",
    "from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig\n",
    "\n",
    "\n",
    "async def setup_chat_with_memory(\n",
    "    kernel: Kernel,\n",
    "    service_id: str,\n",
    ") -> KernelFunction:\n",
    "    prompt = \"\"\"\n",
    "    ChatBot can have a conversation with you about any topic.\n",
    "    It can give explicit instructions or say 'I don't know' if\n",
    "    it does not have an answer.\n",
    "\n",
    "    Information about me, from previous conversations:\n",
    "    - {{recall 'budget by year'}} What is my budget for 2024?\n",
    "    - {{recall 'savings from previous year'}} What are my savings from 2023?\n",
    "    - {{recall 'investments'}} What are my investments?\n",
    "\n",
    "    {{$request}}\n",
    "    \"\"\".strip()\n",
    "\n",
    "    prompt_template_config = PromptTemplateConfig(\n",
    "        template=prompt,\n",
    "        execution_settings={\n",
    "            service_id: kernel.get_service(service_id).get_prompt_execution_settings_class()(service_id=service_id)\n",
    "        },\n",
    "    )\n",
    "\n",
    "    chat_func = kernel.add_function(\n",
    "        function_name=\"chat_with_memory\",\n",
    "        plugin_name=\"TextMemoryPlugin\",\n",
    "        prompt_template_config=prompt_template_config,\n",
    "    )\n",
    "\n",
    "    return chat_func"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "async def chat(kernel: Kernel, chat_func: KernelFunction) -> bool:\n",
    "    try:\n",
    "        user_input = input(\"User:> \")\n",
    "    except KeyboardInterrupt:\n",
    "        print(\"\\n\\nExiting chat...\")\n",
    "        return False\n",
    "    except EOFError:\n",
    "        print(\"\\n\\nExiting chat...\")\n",
    "        return False\n",
    "\n",
    "    if user_input == \"exit\":\n",
    "        print(\"\\n\\nExiting chat...\")\n",
    "        return False\n",
    "\n",
    "    answer = await kernel.invoke(chat_func, request=user_input)\n",
    "\n",
    "    print(f\"ChatBot:> {answer}\")\n",
    "    return True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Populating memory...\")\n",
    "await populate_memory(memory)\n",
    "\n",
    "print(\"Asking questions... (manually)\")\n",
    "await search_memory_examples(memory)\n",
    "\n",
    "print(\"Setting up a chat (with memory!)\")\n",
    "chat_func = await setup_chat_with_memory(kernel, chat_service_id)\n",
    "\n",
    "print(\"Begin chatting (type 'exit' to exit):\\n\")\n",
    "print(\n",
    "    \"Welcome to the chat bot!\\\n",
    "    \\n  Type 'exit' to exit.\\\n",
    "    \\n  Try asking a question about your finances (i.e. \\\"talk to me about my finances\\\").\"\n",
    ")\n",
    "chatting = True\n",
    "while chatting:\n",
    "    chatting = await chat(kernel, chat_func)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Adding documents to your memory\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a dictionary to hold some files. The key is the hyperlink to the file and the value is the file's content:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "github_files = {}\n",
    "github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"] = (\n",
    "    \"README: Installation, getting started, and how to contribute\"\n",
    ")\n",
    "github_files[\n",
    "    \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n",
    "] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n",
    "github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"] = (\n",
    "    \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
    ")\n",
    "github_files[\"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"] = (\n",
    "    \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
    ")\n",
    "github_files[\n",
    "    \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n",
    "] = \"C# class that defines a volatile embedding store\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `save_reference` to save the file:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "COLLECTION = \"SKGitHub\"\n",
    "\n",
    "print(\"Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.\")\n",
    "i = 0\n",
    "for entry, value in github_files.items():\n",
    "    await memory.save_reference(\n",
    "        collection=COLLECTION,\n",
    "        description=value,\n",
    "        text=value,\n",
    "        external_id=entry,\n",
    "        external_source_name=\"GitHub\",\n",
    "    )\n",
    "    i += 1\n",
    "    print(\"  URL {} saved\".format(i))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `search` to ask a question:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ask = \"I love Jupyter notebooks, how should I get started?\"\n",
    "print(\"===========================\\n\" + \"Query: \" + ask + \"\\n\")\n",
    "\n",
    "memories = await memory.search(COLLECTION, ask, limit=5, min_relevance_score=0.77)\n",
    "\n",
    "i = 0\n",
    "for memory in memories:\n",
    "    i += 1\n",
    "    print(f\"Result {i}:\")\n",
    "    print(\"  URL:     : \" + memory.id)\n",
    "    print(\"  Title    : \" + memory.description)\n",
    "    print(\"  Relevance: \" + str(memory.relevance))\n",
    "    print()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
